
// WallClipper.cpp

// Implements the cWallClipper class that performs wall-clipping of a cube moving through a cBlockArea

#include "Globals.h"
#include "WallClipper.h"
#include "Vector3d.h"
#include "BlockArea.h"





/// Rounds a floating-point coord into an integral block coord:
#define ROUND_TO_BLOCK(Coord) ((int)(Coord) - ((Coord > 0) ? 0 : 1))





cWallClipper::cWallClipper(const Vector3d & a_Pos, double a_SizeX, double a_SizeY, double a_SizeZ, Vector3d & a_Direction, const cBlockArea & a_Area) :
	m_Pos(a_Pos),
	m_SizeX(a_SizeX),
	m_SizeY(a_SizeY),
	m_SizeZ(a_SizeZ),
	m_Direction(a_Direction),
	m_Area(a_Area)
{
}





int cWallClipper::Do(const Vector3d & a_Pos, double a_SizeX, double a_SizeY, double a_SizeZ, Vector3d & a_Direction, const cBlockArea & a_Area)
{
	cWallClipper wc(a_Pos, a_SizeX, a_SizeY, a_SizeZ, a_Direction, a_Area);
	return wc.Do();
}





bool cWallClipper::IsBlockPassable(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
{
	// TODO: How to handle special blocks, such as open / closed doors, fencegates and such?
	return !g_BlockIsSolid[a_BlockType];
}





#define CHECKWALLS() \
	int res = CheckWalls(x, y, z); \
	if (res != hwNone) \
	{ \
		return res; \
	}

int cWallClipper::Do(void)
{
	int x = ROUND_TO_BLOCK(m_Pos.x);
	int y = ROUND_TO_BLOCK(m_Pos.y);
	int z = ROUND_TO_BLOCK(m_Pos.z);
	int dx = abs((int)(m_Direction.x * 1000));
	int dy = abs((int)(m_Direction.y * 1000));
	int dz = abs((int)(m_Direction.z * 1000));
	int sx = (m_Direction.x > 0) ? 1 : -1;
	int sy = (m_Direction.y > 0) ? 1 : -1;
	int sz = (m_Direction.z > 0) ? 1 : -1;

	if (dx >= std::max(dy, dz))  // x dominant
	{
		int yd = dy - dx / 2;
		int zd = dz - dx / 2;
		int xend = x + (int)(m_Direction.x + ((m_Direction.x > 0) ? 1 : -1));  // The end x-coord, rounded to the further integral coord
		LOGD("x dominant, yd = %d, zd = %d, x = %d, xend = %d (m_Direction.x = %f)", yd, zd, x, xend, m_Direction.x);

		while (true)
		{
			LOGD("  Wall-checking at x = %d (ending at xend = %d)", x, xend);
			CHECKWALLS();

			if (x == xend)
			{
				break;
			}

			if (yd >= 0)  // move along y
			{
				y += sy;
				yd -= dx;
			}

			if (zd >= 0)  // move along z
			{
				z += sz;
				zd -= dx;
			}

			// move along x
			x  += sx;
			yd += dy;
			zd += dz;
		}  // while (true)
	}
	else if (dy >= std::max(dx, dz))  // y dominant
	{
		int xd = dx - dy / 2;
		int zd = dz - dy / 2;
		int yend = y + (int)(m_Direction.y + ((m_Direction.y > 0) ? 1 : -1));  // The end y-coord, rounded to the further integral coord
		LOGD("y dominant, xd = %d, zd = %d, y = %d, yend = %d (m_Direction.y = %f)", xd, zd, y, yend, m_Direction.y);

		while (true)
		{
			LOGD("  Wall-checking at y = %d (ending at yend = %d)", y, yend);
			CHECKWALLS();

			if (y == yend)
			{
				break;
			}

			if (xd >= 0)  // move along x
			{
				x += sx;
				xd -= dy;
			}

			if (zd >= 0)  // move along z
			{
				z += sz;
				zd -= dy;
			}

			// move along y
			y += sy;
			xd += dx;
			zd += dz;
		}  // while (true)
	}
	else
	{
		// z dominant
		ASSERT(dz >= std::max(dx, dy));
		int xd = dx - dz / 2;
		int yd = dy - dz / 2;
		int zend = z + (int)(m_Direction.z + ((m_Direction.z > 0) ? 1 : -1));  // The end z-coord, rounded to the further integral coord
		LOGD("z dominant, xd = %d, yd = %d, z = %d, zend = %d (m_Direction.z = %f)", xd, yd, z, zend, m_Direction.z);

		while (true)
		{
			LOGD("  Wall-checking at z = %d (ending at zend = %d)", z, zend);
			CHECKWALLS();

			if (z == zend)
			{
				break;
			}

			if (xd >= 0)  // move along x
			{
				x += sx;
				xd -= dz;
			}

			if (yd >= 0)  // move along y
			{
				y += sy;
				yd -= dz;
			}
			
			// move along z
			z += sz;
			xd += dx;
			yd += dy;
		}  // while (true)
	}  // if (which dimension is dominant)
	
	return true;
}





int cWallClipper::CheckWalls(int a_PosX, int a_PosY, int a_PosZ)
{
	int res = hwNone;
	
	res |= CheckXWall(a_PosX, a_PosY, a_PosZ);
	res |= CheckYWall(a_PosX, a_PosY, a_PosZ);
	res |= CheckZWall(a_PosX, a_PosY, a_PosZ);
	
	return res;
}





int cWallClipper::CheckXWall(int a_PosX, int a_PosY, int a_PosZ)
{
	int xpos = a_PosX + m_WallXOffset;
	for (int y = 0; y < m_WallSizeY; y++)
	{
		int ypos = a_PosY + y;
		for (int z = 0; z < m_WallSizeZ; z++)
		{
			BLOCKTYPE  BlockType;
			NIBBLETYPE BlockMeta;
			m_Area.GetBlockTypeMeta(xpos, ypos, a_PosZ + z, BlockType, BlockMeta);
			if (!IsBlockPassable(BlockType, BlockMeta))
			{
				return hwX;
				// Clip m_Direction by a_PosX:
				ClipDirection(a_PosX - m_Pos.x, m_Direction.y, m_Direction.z);
			}
		}  // for z
	}  // for y
	return hwNone;
}





int cWallClipper::CheckYWall(int a_PosX, int a_PosY, int a_PosZ)
{
	int ypos = a_PosY + m_WallXOffset;
	for (int x = 0; x < m_WallSizeX; x++)
	{
		int xpos = a_PosX + x;
		for (int z = 0; z < m_WallSizeZ; z++)
		{
			BLOCKTYPE  BlockType;
			NIBBLETYPE BlockMeta;
			m_Area.GetBlockTypeMeta(xpos, ypos, a_PosZ + z, BlockType, BlockMeta);
			if (!IsBlockPassable(BlockType, BlockMeta))
			{
				return hwY;
				// TODO: Clip m_Direction by a_PosY
			}
		}  // for z
	}  // for x
	return hwNone;
}





int cWallClipper::CheckZWall(int a_PosX, int a_PosY, int a_PosZ)
{
	int zpos = a_PosZ + m_WallZOffset;
	for (int x = 0; x < m_WallSizeX; x++)
	{
		int xpos = a_PosX + x;
		for (int y = 0; y < m_WallSizeY; y++)
		{
			BLOCKTYPE  BlockType;
			NIBBLETYPE BlockMeta;
			m_Area.GetBlockTypeMeta(xpos, a_PosY + y, zpos, BlockType, BlockMeta);
			if (!IsBlockPassable(BlockType, BlockMeta))
			{
				return hwZ;
				// TODO: Clip m_Direction by a_PosZ
			}
		}  // for y
	}  // for x
	return hwNone;
}





void cWallClipper::ClipDirection(double a_ClipX, double a_ClipY, double a_ClipZ)
{
	if (abs(m_Direction.x) > abs(a_ClipX))
	{
		m_Direction.y = m_Direction.y * a_ClipX / m_Direction.x;
		m_Direction.z = m_Direction.z * a_ClipX / m_Direction.x;
		m_Direction.x = a_ClipX;
	}

	if (abs(m_Direction.y) > abs(a_ClipY))
	{
		m_Direction.x = m_Direction.x * a_ClipY / m_Direction.y;
		m_Direction.z = m_Direction.z * a_ClipY / m_Direction.y;
		m_Direction.y = a_ClipY;
	}

	if (abs(m_Direction.z) > abs(a_ClipZ))
	{
		m_Direction.y = m_Direction.y * a_ClipZ / m_Direction.z;
		m_Direction.x = m_Direction.x * a_ClipZ / m_Direction.z;
		m_Direction.z = a_ClipZ;
	}
}




